// Copyright (C) 2010 David Sugar, Tycho Softworks.
//
// This file is part of GNU uCommon C++.
//
// GNU uCommon C++ is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU uCommon C++ is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with GNU uCommon C++.  If not, see <http://www.gnu.org/licenses/>.

#include <ucommon/secure.h>
#include <ctype.h>
#include <sys/stat.h>

using namespace UCOMMON_NAMESPACE;

static shell::flagopt helpflag('h',"--help",    _TEXT("display this list"));
static shell::flagopt althelp('?', NULL, NULL);
static shell::flagopt binary('b', "--binary", _TEXT("only display binary data"));

static const char *argv0 = "hexdump";

static void report(const char *path, int code)
{
    const char *err = _TEXT("i/o error");

    switch(code) {
    case EACCES:
    case EPERM:
        err = _TEXT("permission denied");
        break;
    case EROFS:
        err = _TEXT("read-only file system");
        break;
    case ENODEV:
    case ENOENT:
        err = _TEXT("no such file or directory");
        break;
    case ENOTDIR:
        err = _TEXT("not a directory");
        break;
    case ENOTEMPTY:
        err = _TEXT("directory not empty");
        break;
    case ENOSPC:
        err = _TEXT("no space left on device");
        break;
    case EBADF:
    case ENAMETOOLONG:
        err = _TEXT("bad file path");
        break;
    case EBUSY:
    case EINPROGRESS:
        err = _TEXT("file or directory busy");
        break;
    case EINTR:
        err = _TEXT("operation interupted");
        break;
    case EINVAL:
        err = _TEXT("file is not a valid link");
        break;
#ifdef  ELOOP
    case ELOOP:
        err = _TEXT("too many sym links");
        break;
#endif
    }

    shell::errexit(1, "*** %s: %s: %s\n", argv0, path, err);
}

void dump(const char *path)
{
    fsys_t fs;
    uint8_t buf[32];
    ssize_t count;
    size_t width = 16;
    ssize_t pos;
    unsigned long addr = 0;

    if(path) {
        fs.open(path, fsys::STREAM);
        if(!is(fs))
            report(path, fs.err());
    }
    else {
        path = "-";
        fs.set(shell::input());
    }

    if(is(binary))
        width = 32;

    for(;;) {
        count = fs.read(buf, width);
        if(count < 0)
            report(path, fs.err());

        if(!count)
            break;

        printf("%08lx:", addr);
        addr += width;

        for(pos = 0; pos < count; ++pos) {
            if(is(binary) && pos == 16)
                fputc(' ', stdout);
            if(is(binary))
                printf("%02x", buf[pos]);
            else
                printf("%02x ", buf[pos]);
        }

        if(is(binary)) {
            fputc('\n', stdout);
            continue;
        }

        while(((size_t)(pos++)) < width)
            printf("   ");

        for(pos = 0; pos < count; ++pos) {
            uint8_t ch = buf[pos] & 0x7f;
            if(ch < 32)
                ch = '.';
            fputc(ch, stdout);
        }
        fputc('\n', stdout);
    }

    if(*fs == shell::input())
        fs.release();
    else
        fs.close();
}

PROGRAM_MAIN(argc, argv)
{
    int err = 0;
    const char *path;
    int errcode = 0;

    shell::bind("hexdump");
    shell args(argc, argv);
    argv0 = args.argv0();

    if(is(helpflag) || is(althelp)) {
err:
        printf("%s: %s %s\n", _TEXT("Usage"), argv0, _TEXT("[options] [path]"));
        printf("%s\n\n", _TEXT("Hex dump of file or stdin"));
        printf("%s\n", _TEXT("Options:"));
        shell::help();
        printf("\n%s\n", _TEXT("Report bugs to dyfet@gnu.org"));
        PROGRAM_EXIT(errcode);
    }

    if(args() > 1) {
        errcode = 2;
        goto err;
    }

    if(args() == 1) {
        path = args[0];
        dump(path);
    }
    else
        dump(NULL);

    if(err)
        report(path, err);

    PROGRAM_EXIT(err);
}

